package ar.edu.caece.pl.asin.manager.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ar.edu.caece.pl.alex.model.IToken;
import ar.edu.caece.pl.alex.model.impl.Token;
import ar.edu.caece.pl.asin.manager.IAnalizadorSintactico;
import ar.edu.caece.pl.asin.manager.IErrorManager;
import ar.edu.caece.pl.asin.model.ITokenStream;
import ar.edu.caece.pl.asin.model.impl.BloqueVarParam;
import ar.edu.caece.pl.asin.model.impl.BloqueVarParam1;
import ar.edu.caece.pl.asin.model.impl.C1;
import ar.edu.caece.pl.asin.model.impl.CallingParams;
import ar.edu.caece.pl.asin.model.impl.E1;
import ar.edu.caece.pl.asin.model.impl.Else;
import ar.edu.caece.pl.asin.model.impl.Expresion;
import ar.edu.caece.pl.asin.model.impl.Factor;
import ar.edu.caece.pl.asin.model.impl.Factor1;
import ar.edu.caece.pl.asin.model.impl.Factor2;
import ar.edu.caece.pl.asin.model.impl.Header;
import ar.edu.caece.pl.asin.model.impl.ProcsFuncs;
import ar.edu.caece.pl.asin.model.impl.Sentencia;
import ar.edu.caece.pl.asin.model.impl.Sentencia1;
import ar.edu.caece.pl.asin.model.impl.ShowElem;
import ar.edu.caece.pl.asin.model.impl.ShowElementNext;
import ar.edu.caece.pl.asin.model.impl.T1;
import ar.edu.caece.pl.asin.model.impl.Termino;
import ar.edu.caece.pl.asin.model.impl.Tipo;
import ar.edu.caece.pl.asin.model.impl.TipoC;
import ar.edu.caece.pl.asin.model.impl.TipoC1;
import ar.edu.caece.pl.asin.model.impl.Var;
import ar.edu.caece.pl.asin.model.impl.Var1;
import ar.edu.caece.pl.asin.model.impl.Var2;
import ar.edu.caece.pl.asin.model.impl.Var3;

public class ErrorManager implements IErrorManager {
	
	private static final ErrorManager instance = new ErrorManager();
	private boolean debugEnabled = false;
	private static final Map<Class<? extends IAnalizadorSintactico>, List<IToken>> siguientesMap = new HashMap<Class<? extends IAnalizadorSintactico>, List<IToken>>();
	
	static {
		List <IToken> siguientes = new ArrayList<IToken>();
		
		//PROGRAMA
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(AnalizadorSintactico.class, siguientes);
		siguientes.clear();
		
		//HEADER
		//S(HEADER) = { PROCEDURE, FUNCTION, CONST, VAR, EOF }
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_PROCEDURE));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_FUNCTION));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_CONST));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_VAR));
		siguientesMap.put(Header.class, siguientes);
		siguientes.clear();
		
		//TIPO_C
		//S(TIPO_C) = { CONST, VAR, NATURAL, INTEGER }
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_CONST));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_VAR));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_NATURAL));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_INTEGER));
		siguientesMap.put(TipoC.class, siguientes);
		siguientes.clear();
		
		//TIPO_C1
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(TipoC1.class, siguientes);
		siguientes.clear();
		
		//VAR
		//S(VAR) = { CONST, VAR, IDENTIFICADOR }
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_CONST));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_VAR));
		siguientes.add(new Token(IToken.TYPE_IDENTIFICADOR));
		siguientesMap.put(Var.class, siguientes);
		siguientes.clear();
		
		//VAR_1
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(Var1.class, siguientes);
		siguientes.clear();
		
		//VAR_2
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(Var2.class, siguientes);
		siguientes.clear();
		
		//VAR_3
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(Var3.class, siguientes);
		siguientes.clear();
		
		//TIPO
		//S(TIPO) = { ";" , ",", ), BYREF, BYVAL, IDENTIFICADOR}
		siguientes.add(new Token(IToken.TYPE_PUNTO_COMA));
		siguientes.add(new Token(IToken.TYPE_COMA));
		siguientes.add(new Token(IToken.TYPE_PARENTESIS_DER));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_BYREF));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_BYVAL));
		siguientes.add(new Token(IToken.TYPE_IDENTIFICADOR));
		siguientesMap.put(Tipo.class, siguientes);
		siguientes.clear();
		
		//PROCS_FUNCS
		//S(PROCS_FUNCS) = { EOF, PROCEDURE, FUNCTION }
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_PROCEDURE));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_FUNCTION));
		siguientesMap.put(ProcsFuncs.class, siguientes);
		siguientes.clear();
		
		//BLOQUE_VAR_PARAM
		//S(BLOQUE_VAR_PARAM) = { ), BYREF, BYVAL, IDENTIFICADOR}
		siguientes.add(new Token(IToken.TYPE_PARENTESIS_DER));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_BYREF));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_BYVAL));
		siguientes.add(new Token(IToken.TYPE_IDENTIFICADOR));
		siguientesMap.put(BloqueVarParam.class, siguientes);
		siguientes.clear();
		
		//BLOQUE_VAR_PARAM_1
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(BloqueVarParam1.class, siguientes);
		siguientes.clear();
		
//		//SENTENCIAS
//		//S(SENTENCIAS) = { END-PROC, END FUNC, IDENTIFICADOR, IF, WHILE, SHOWLN, SHOW, READ , END-WHILE, ELSE, ENDIF, ";" }
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_END_PROC));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_END_FUNC));
//		siguientes.add(new Token(IToken.TYPE_IDENTIFICADOR));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_IF));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_WHILE));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_SHOWLN));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_SHOW));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_READ));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_END_WHILE));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_ELSE));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_END_IF));
//		siguientes.add(new Token(IToken.TYPE_PUNTO_COMA));
//		siguientesMap.put(Sentencias.class, siguientes);
//		siguientes.clear();
		
		//SENTENCIA
		//S(SENTENCIA) = { ";" }
		siguientes.add(new Token(IToken.TYPE_PUNTO_COMA));
		siguientesMap.put(Sentencia.class, siguientes);
		siguientes.clear();
		
		//SENTENCIA_1
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(Sentencia1.class, siguientes);
		siguientes.clear();
		
//		//SENTENCIA_2
//		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
//		siguientesMap.put(Sentencia2.class, siguientes);
//		siguientes.clear();
		
		//SENTENCIA_3
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(ShowElem.class, siguientes);
		siguientes.clear();
		
		//SHOW_ELEM_NEXT
		//S(SHOW_ELEM_NEXT) = { ";", ","  }
		siguientes.add(new Token(IToken.TYPE_PUNTO_COMA));
		siguientes.add(new Token(IToken.TYPE_COMA));
		siguientesMap.put(ShowElementNext.class, siguientes);
		siguientes.clear();
		
		//CALLING_PARAMS
		//S(CALLING_PARAMS) = { ), ","  }
		siguientes.add(new Token(IToken.TYPE_PARENTESIS_DER));
		siguientes.add(new Token(IToken.TYPE_COMA));
		siguientesMap.put(CallingParams.class, siguientes);
		siguientes.clear();
		
		//ELSE
		//S(ELSE) = { ENDIF }
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_END_IF));
		siguientesMap.put(Else.class, siguientes);
		siguientes.clear();
		
//		//ASIGNACION
//		//S(ASIGNACION) = { ";" }
//		siguientes.add(new Token(IToken.TYPE_PUNTO_COMA));
//		siguientesMap.put(Asignacion.class, siguientes);
//		siguientes.clear();
//		
//		//ASIGNACION_1
//		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
//		siguientesMap.put(Asignacion1.class, siguientes);
//		siguientes.clear();
//		
//		//ASIGNACION_2
//		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
//		siguientesMap.put(Asignacion2.class, siguientes);
//		siguientes.clear();
//		
//		//CONDICION
//		//S(CONDICION) = { THEN, DO }
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_THEN));
//		siguientes.add(new Token(IToken.PALABRA_RESERVADA_DO));
//		siguientesMap.put(Condicion.class, siguientes);
//		siguientes.clear();
//		
//		//CONDICION_1
//		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
//		siguientesMap.put(Condicion1.class, siguientes);
//		siguientes.clear();
//		
//		//CONDICION_2
//		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
//		siguientesMap.put(Condicion2.class, siguientes);
//		siguientes.clear();
		
		//C1
		//S(C1) = { THEN, DO  }
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_THEN));
		siguientes.add(new Token(IToken.PALABRA_RESERVADA_DO));
		siguientesMap.put(C1.class, siguientes);
		siguientes.clear();
		
		//EXP
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(Expresion.class, siguientes);
		siguientes.clear();
		
		//TERMINO
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(Termino.class, siguientes);
		siguientes.clear();
		
		//T1
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(T1.class, siguientes);
		siguientes.clear();
		
		//E1
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(E1.class, siguientes);
		siguientes.clear();
		
		//FACTOR
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(Factor.class, siguientes);
		siguientes.clear();
		
		//FACTOR1
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(Factor1.class, siguientes);
		siguientes.clear();
		
		//FACTOR2
		siguientes.add(new Token(IToken.TYPE_FIN_DE_ARCHIVO));
		siguientesMap.put(Factor2.class, siguientes);
		siguientes.clear();
		
	}

	private ErrorManager() {
	}  //Singleton


	public static IErrorManager getInstance(boolean debug) {
		
		instance.setDebugMode(debug);
		return instance;
		
	}
	
	public void setDebugMode(boolean status) {
		
		this.debugEnabled = status;
	}
	
	public void error ( String message, IToken faultyToken ) {
		this.consoleLog(message, faultyToken, false);
	}

	public void debug(String message, IToken debuggingToken) {
		if (this.debugEnabled ) {
			this.consoleLog(message, debuggingToken, true);
		}
	}

	public void error(String message) {
		this.consoleLog(message, null, false);
	}


	public void error(IToken faultyToken) {
		this.consoleLog(null, faultyToken, false);
	}

	public void debug(String message) {
		if (this.debugEnabled ) {
			this.consoleLog(message, null, true);
		}
	}


	public void debug(IToken debuggingToken) {
		if (this.debugEnabled ) {
			this.consoleLog(null, debuggingToken, true);
		}
	}
	
	/**
	 * Imprime por consola
	 * @param message, Mensaje particular
	 * @param token, El token actual
	 * @param mode (sysout=true / syserr=false)
	 */
	private void consoleLog ( String message, IToken token, boolean mode) {
		
		if ( message == null && token == null ) {
			return;
		}
		StringBuilder sb = new StringBuilder();
		
		if ( message != null ) {
			sb.append(message).append("\n");
		}
		
		if ( token != null ) {
			sb.append( token.toString() );
		}
		
		if (mode) {
			System.out.println(sb.toString());
		} else {
			System.err.println(sb.toString());
		}
		
	}
	
	public void recover(Class<? extends IAnalizadorSintactico> noTerminalClass, ITokenStream tokenStream) {

		boolean encontrado = false;
		
		//El siguiente no era lo que buscaba, asi que avanzo hasta encontrar lo que buscaba.
		
		do {
			tokenStream.leer();
			
			//Agrego info de debug del token leido
			this.debug( tokenStream.getTokenSiguiente() );
			
			//Busco en los siguientes
			for ( IToken t : siguientesMap.get(noTerminalClass) ) {
				if (t.equals(tokenStream.getTokenSiguiente())) {
					encontrado = true;
					break; //break del for()
				}
			}
			
		} while (!encontrado && !tokenStream.getTokenActual().equals( new Token (IToken.TYPE_FIN_DE_ARCHIVO)));
	}
}